@gpc-cli/core 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { OutputFormat, ResolvedConfig, WebhookConfig } from '@gpc-cli/config';
2
2
  import { AuthClient } from '@gpc-cli/auth';
3
3
  import { GpcPlugin, PluginManifest, CommandEvent, CommandResult, PluginError, RequestEvent, ResponseEvent, PluginCommand } from '@gpc-cli/plugin-sdk';
4
- import { PlayApiClient, EditCommitOptions, Track, ExternallyHostedApk, ExternallyHostedApkResponse, DeobfuscationFileType, UploadProgressEvent, ResumableUploadOptions, Listing, ImageType, CountryAvailability, Image, AppDetails, Review, ReviewReplyResponse, Subscription, SubscriptionOffer, OffersListResponse, BasePlanMigratePricesRequest, InAppProduct, Order, SubscriptionDeferResponse, SubscriptionsV2DeferResponse, ProductPurchase, ProductPurchaseV2, SubscriptionPurchaseV2, VoidedPurchase, UsersApiClient, User, DeveloperLevelPermission, Grant, MetricRow, ReportingDimension, ReportingAggregation, VitalsMetricSet, ReportingApiClient, AnomalyDetectionResponse, MetricSetResponse, ErrorIssuesResponse, ConvertRegionPricesResponse, ReportType, StatsDimension, Testers, AppRecoveryTargeting, AppRecoveryAction, CreateAppRecoveryActionRequest, DataSafetyResponse, DataSafetyRequest, ExternalTransaction, ExternalTransactionRefund, DeviceTierConfig, OneTimeOffer, OneTimeProduct, OneTimeOffersListResponse, OneTimeProductsListResponse, GamesConfigClient, AchievementConfiguration, LeaderboardConfiguration, AchievementConfigurationListResponse, GamesApiClient, Achievement, LeaderboardConfigurationListResponse, Leaderboard, ImageConfiguration, EnterpriseApiClient, CustomApp, GeneratedApksPerSigningKey, Bundle } from '@gpc-cli/api';
4
+ import { PlayApiClient, EditCommitOptions, Track, ExternallyHostedApk, ExternallyHostedApkResponse, DeobfuscationFileType, UploadProgressEvent, ResumableUploadOptions, Listing, ImageType, CountryAvailability, Image, AppDetails, Review, ReviewReplyResponse, Subscription, SubscriptionOffer, OffersListResponse, BasePlanMigratePricesRequest, InAppProduct, ConsumptionUsageEvent, RefundPreference, Order, SubscriptionDeferResponse, SubscriptionsV2DeferResponse, ProductPurchase, ProductPurchaseV2, SubscriptionPurchaseV2, VoidedPurchase, UsersApiClient, User, DeveloperLevelPermission, Grant, MetricRow, ReportingDimension, ReportingAggregation, VitalsMetricSet, ReportingApiClient, AnomalyDetectionResponse, MetricSetResponse, ErrorIssuesResponse, ConvertRegionPricesResponse, ReportType, StatsDimension, Testers, AppRecoveryTargeting, AppRecoveryAction, CreateAppRecoveryActionRequest, DataSafetyResponse, DataSafetyRequest, ExternalTransaction, ExternalTransactionRefund, DeviceTierConfig, OneTimeOffer, OneTimeProduct, OneTimeOffersListResponse, OneTimeProductsListResponse, GamesConfigClient, AchievementConfiguration, LeaderboardConfiguration, AchievementConfigurationListResponse, GamesApiClient, Achievement, LeaderboardConfigurationListResponse, Leaderboard, ImageConfiguration, EnterpriseApiClient, CustomApp, GeneratedApksPerSigningKey, Bundle } from '@gpc-cli/api';
5
5
 
6
6
  declare class GpcError extends Error {
7
7
  readonly code: string;
@@ -367,7 +367,9 @@ declare function pushListings(client: PlayApiClient, packageName: string, dir: s
367
367
  commitOptions?: EditCommitOptions;
368
368
  }): Promise<PushResult | DryRunResult>;
369
369
  declare function listImages(client: PlayApiClient, packageName: string, language: string, imageType: ImageType): Promise<Image[]>;
370
- declare function uploadImage(client: PlayApiClient, packageName: string, language: string, imageType: ImageType, filePath: string, commitOptions?: EditCommitOptions): Promise<Image>;
370
+ declare function uploadImage(client: PlayApiClient, packageName: string, language: string, imageType: ImageType, filePath: string, commitOptions?: EditCommitOptions, uploadOptions?: {
371
+ aiGenerated?: boolean;
372
+ }): Promise<Image>;
371
373
  declare function deleteImage(client: PlayApiClient, packageName: string, language: string, imageType: ImageType, imageId: string, commitOptions?: EditCommitOptions): Promise<void>;
372
374
  declare function diffListingsCommand(client: PlayApiClient, packageName: string, dir: string): Promise<ListingDiff[]>;
373
375
  declare function getCountryAvailability(client: PlayApiClient, packageName: string, track: string): Promise<CountryAvailability>;
@@ -661,6 +663,32 @@ declare function batchGetOrders(client: PlayApiClient, packageName: string, orde
661
663
  declare function getProductPurchaseV2(client: PlayApiClient, packageName: string, token: string): Promise<ProductPurchaseV2>;
662
664
  declare function cancelSubscriptionV2(client: PlayApiClient, packageName: string, token: string, cancellationType?: string): Promise<void>;
663
665
  declare function deferSubscriptionV2(client: PlayApiClient, packageName: string, token: string, deferDuration: string, etag?: string): Promise<SubscriptionsV2DeferResponse>;
666
+ /** CLI-facing refund preference, mapped to the Google Play API enum. */
667
+ type RefundReviewPreference = "approve" | "decline" | "neutral";
668
+ interface ReviewOrderRefundOptions {
669
+ /** Token from the PendingRefundReviewNotification RTDN. */
670
+ pendingRefundToken: string;
671
+ preference: RefundReviewPreference;
672
+ /** Whether a free sample, trial, or functionality info was offered before purchase. */
673
+ sampleContentProvided: boolean;
674
+ consumptionPercentageMilliunits?: number;
675
+ consumptionUsageEvents?: ConsumptionUsageEvent[];
676
+ }
677
+ interface ReviewOrderRefundResult {
678
+ packageName: string;
679
+ orderId: string;
680
+ refundPreference: RefundPreference;
681
+ sampleContentProvided: boolean;
682
+ consumptionPercentageMilliunits?: number;
683
+ consumptionUsageEventCount: number;
684
+ submitted: boolean;
685
+ }
686
+ /**
687
+ * Respond to a chargeback review (PendingRefundReviewNotification) with a refund
688
+ * preference and optional purchase-usage evidence. Google Play expects a response
689
+ * within 24 hours of the notification.
690
+ */
691
+ declare function reviewOrderRefund(client: PlayApiClient, packageName: string, orderId: string, options: ReviewOrderRefundOptions): Promise<ReviewOrderRefundResult>;
664
692
 
665
693
  declare const PERMISSION_PROPAGATION_WARNING = "Note: Permission changes may take up to 48 hours to propagate.";
666
694
  interface ListUsersOptions {
@@ -1005,7 +1033,9 @@ declare function refundExternalTransaction(client: PlayApiClient, packageName: s
1005
1033
 
1006
1034
  declare function listDeviceTiers(client: PlayApiClient, packageName: string): Promise<DeviceTierConfig[]>;
1007
1035
  declare function getDeviceTier(client: PlayApiClient, packageName: string, configId: string): Promise<DeviceTierConfig>;
1008
- declare function createDeviceTier(client: PlayApiClient, packageName: string, config: DeviceTierConfig): Promise<DeviceTierConfig>;
1036
+ declare function createDeviceTier(client: PlayApiClient, packageName: string, config: DeviceTierConfig, options?: {
1037
+ allowUnknownDevices?: boolean;
1038
+ }): Promise<DeviceTierConfig>;
1009
1039
 
1010
1040
  declare function listOneTimeProducts(client: PlayApiClient, packageName: string): Promise<OneTimeProductsListResponse>;
1011
1041
  declare function getOneTimeProduct(client: PlayApiClient, packageName: string, productId: string): Promise<OneTimeProduct>;
@@ -1548,6 +1578,8 @@ interface WatchOptions {
1548
1578
  render: (status: AppStatus) => string;
1549
1579
  fetch: () => Promise<AppStatus>;
1550
1580
  save: (status: AppStatus) => Promise<void>;
1581
+ /** Runs after each successful fetch+save cycle (e.g. breach notifications). */
1582
+ onStatus?: (status: AppStatus) => Promise<void> | void;
1551
1583
  }
1552
1584
  declare function loadStatusCache(packageName: string, ttlSeconds?: number): Promise<AppStatus | null>;
1553
1585
  declare function saveStatusCache(packageName: string, data: AppStatus, ttlSeconds?: number): Promise<void>;
@@ -1769,6 +1801,16 @@ interface DecodedNotification {
1769
1801
  productType: number;
1770
1802
  refundType?: number;
1771
1803
  };
1804
+ /** Chargeback review request — respond within 24h via `orders review-refund`. */
1805
+ pendingRefundReviewNotification?: {
1806
+ version: string;
1807
+ pendingRefundToken: string;
1808
+ orderId: string;
1809
+ /** Pending reviews currently only use CHARGEBACK (7). */
1810
+ refundReason?: number;
1811
+ obfuscatedAccountId?: string;
1812
+ obfuscatedProfileId?: string;
1813
+ };
1772
1814
  testNotification?: {
1773
1815
  version: string;
1774
1816
  };
@@ -1825,6 +1867,8 @@ interface ImageSyncOptions {
1825
1867
  delete?: boolean;
1826
1868
  dryRun?: boolean;
1827
1869
  commitOptions?: EditCommitOptions;
1870
+ /** Attest that the synced images were (or were not) generated by AI. */
1871
+ aiGenerated?: boolean;
1828
1872
  }
1829
1873
  interface ImageSyncDetail {
1830
1874
  language: string;
@@ -1933,4 +1977,4 @@ declare function readinessBadgeColor(grade: ReadinessGrade): string;
1933
1977
  declare function readinessBadgeUrl(score: ReadinessScore): string;
1934
1978
  declare function readinessBadgeMarkdown(score: ReadinessScore): string;
1935
1979
 
1936
- export { ApiError, type ApiSigningFingerprint, type AppInfo, type AppStatus, type ApplyReleaseNotesResult, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type BundlesWaitOptions, type ChangelogEntry, type ChecklistInput, type ChecklistItem, type ChecklistResult, type CommandContext, type CommitCluster, type CommitResult, ConfigError, type CreateEnterpriseAppParams, DEFAULT_LIMITS, DEFAULT_MODELS, DEFAULT_PREFLIGHT_CONFIG, DEFAULT_WATCH_THRESHOLDS, type DecodedNotification, type DiffToken, type DiscoverPluginsOptions, type DiscoveredPlugin, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ErrorReason, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FinancialReportDownload, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GameConfigDiff, type GamesPushOptions, type GamesSyncResult, type GenerateOptions, type GeneratedChangelog, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, type GitRunner, GpcError, type ImageSyncDetail, type ImageSyncOptions, type ImageSyncResult, type ImageValidationResult, type InitOptions, type InitResult, type InternalSharingUploadResult, type KeystoreFingerprint, type ListGamesConfigOptions, type ListIapOptions, type ListReportsResult, type ListSubscriptionsOptions, type ListUsersOptions, type ListVoidedOptions, type ListingDiff, type ListingFieldLimits, type ListingLintResult, type ListingsResult, type LoadedPlugin, type LocaleBundle, type LocaleEntry, type MigrationResult, NetworkError, type OneTimeProductDiff, type OutputMode, PERMISSION_PROPAGATION_WARNING, PLACEHOLDER_TEXT, PLAY_STORE_LIMIT, PROVIDER_WHITELIST, type ParsedCommit, type ParsedManifest, type ParsedMonth, type PlayStoreFormat, type PlayStoreRenderOptions, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type Provider, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, RENDERERS, REVIEW_SORT_PRESETS, type RawCommit, type ReadinessBreakdownEntry, type ReadinessGrade, type ReadinessScore, type ReadinessSignal, type ReadinessSignalStatus, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type Renderer, type ReportObject, type ReportsAuth, type ResolveAiConfigOptions, type ResolveLocalesOptions, type ReviewAnalysis, type ReviewExportOptions, type ReviewSortPreset, type ReviewsFilterOptions, type RevocationRefundType, type RtdnStatus, SECTION_ORDER, SENSITIVE_ARG_KEYS, SENSITIVE_KEYS, SEVERITY_ORDER, type ScaffoldOptions, type ScaffoldResult, type SigningConsistencyResult, type SigningKeyComparison, type Spinner, type StatsReportDownload, type StatusDiff, type StatusRelease, type StatusReviews, type StatusVitalMetric, type SubscriptionAnalytics, type SubscriptionDiff, type SyncResult, type ThresholdResult, type TrainConfig, type TrainState, type TranslateBundleOptions, type TranslatedBundle, type TranslationFailure, type TranslationPath, type TranslationResult, type Translator, type TranslatorConfig, type UploadResult, VALID_WATCH_METRICS, type ValidateCheck, type ValidateOptions, type ValidateResult, type ValidatedPluginApproval, type VersionVitalsComparison, type VersionVitalsRow, type VitalsOverview, type VitalsQueryOptions, type VitalsTrendComparison, type WatchAction, type WatchCallbacks, type WatchConfig, type WatchEvent, type WatchMetric, type WatchOptions, type WatchRollout, type WatchSummary, type WatchVitalReading, type WatchVitalsOptions, type WebhookPayload, type ZipCsvEntry, abortTrain, acknowledgeProductPurchase, activateBasePlan, activateOffer, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, annotateListResult, applyReleaseNotes, assignRelease, batchGetOrders, batchSyncInAppProducts, buildChecklist, buildLocaleBundle, bundleToReleaseNotes, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkSigningConsistency, checkThreshold, classifyError, clearAuditLog, commitWithRescue, compareBundles, compareFingerprints, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAchievementConfig, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createLeaderboardConfig, createOffer, createOneTimeOffer, createOneTimeProduct, createRecoveryAction, createSpinner, createSubscription, createTrack, createTranslator, deactivateBasePlan, deactivateOffer, decodeCsvText, decodeNotification, decodeStatsCsv, defaultGitRunner, deferSubscriptionPurchase, deferSubscriptionV2, deleteAchievementConfig, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteLeaderboardConfig, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffAchievementConfig, diffLeaderboardConfig, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPluginEntries, discoverPlugins, dispatchWebhook, downloadFinancialReport, downloadGeneratedApk, downloadReportObject, downloadStatsReport, exportImages, exportReviews, extractCsvEntriesFromZip, fetchAggregateCost, fetchChangelog, fetchReleaseNotes, findBundle, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatMoney, formatNotification, formatOutput, formatPathLabel, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateChangelog, generateMigrationPlan, generateNotesFromGit, getAchievementConfig, getAllScannerNames, getApiSigningFingerprint, getAppInfo, getAppStatus, getCountryAvailability, getDeviceTier, getExternalTransaction, getInAppProduct, getKeystoreFingerprint, getLeaderboardConfig, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getQuotaUsage, getReleasesStatus, getReview, getRtdnStatus, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsErrorCount, getVitalsLmk, getVitalsMemory, getVitalsOverview, getVitalsRendering, getVitalsStartup, gradeFromPercent, gunzipIfNeeded, handleBreach, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isGzip, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, isVersionedNotesDir, isZip, lintListing, lintListings, lintLocalListings, listAchievementConfigs, listAchievements, listAuditEvents, listBundles, listDeviceTiers, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboardConfigs, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listRecoveryActions, listReportObjects, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, monthToken, moreResultsFooter, normalizeFingerprint, parseAppfile, parseCommit, parseFastfile, parseGrantArg, parseKeytoolOutput, parseMonth, parseRemoteUrl, pauseTrain, promoteRelease, publish, publishEnterpriseApp, pullAchievementConfigs, pullLeaderboardConfigs, pullListings, pushAchievementConfigs, pushLeaderboardConfigs, pushListings, readListingsFromDir, readReleaseNotesForVersion, readReleaseNotesFromDir, readinessBadgeColor, readinessBadgeMarkdown, readinessBadgeUrl, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, relativeTime, removeTesters, removeUser, renderChecklistMarkdown, renderJson, renderMarkdown, renderPlayStore, renderPlayStoreJson, renderPlayStoreMd, renderPlayStorePrompt, renderPrompt, replyToReview, reportAccessDeniedError, reportBucketNotFoundError, reportObjectNotFoundError, reportPrefix, resolveAiConfig, resolveLocales, resolveReportsBucket, retryOnUploadNotComplete, revokeSubscriptionPurchase, runPreflight, runWatch, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, scoreReadiness, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, setAchievementIcon, setLeaderboardIcon, sha256File, sortResults, sortReviews, startTrain, statusHasBreach, syncImages, syncInAppProducts, topFiles, trackBreachState, translateBundle, updateAchievementConfig, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateLeaderboardConfig, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateAndCommit, validateBundleForApply, validateImage, validateLanguageCode, validatePackageName, validatePluginForApproval, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, waitForBundle, waitForBundleProcessing, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
1980
+ export { ApiError, type ApiSigningFingerprint, type AppInfo, type AppStatus, type ApplyReleaseNotesResult, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type BundlesWaitOptions, type ChangelogEntry, type ChecklistInput, type ChecklistItem, type ChecklistResult, type CommandContext, type CommitCluster, type CommitResult, ConfigError, type CreateEnterpriseAppParams, DEFAULT_LIMITS, DEFAULT_MODELS, DEFAULT_PREFLIGHT_CONFIG, DEFAULT_WATCH_THRESHOLDS, type DecodedNotification, type DiffToken, type DiscoverPluginsOptions, type DiscoveredPlugin, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ErrorReason, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FinancialReportDownload, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GameConfigDiff, type GamesPushOptions, type GamesSyncResult, type GenerateOptions, type GeneratedChangelog, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, type GitRunner, GpcError, type ImageSyncDetail, type ImageSyncOptions, type ImageSyncResult, type ImageValidationResult, type InitOptions, type InitResult, type InternalSharingUploadResult, type KeystoreFingerprint, type ListGamesConfigOptions, type ListIapOptions, type ListReportsResult, type ListSubscriptionsOptions, type ListUsersOptions, type ListVoidedOptions, type ListingDiff, type ListingFieldLimits, type ListingLintResult, type ListingsResult, type LoadedPlugin, type LocaleBundle, type LocaleEntry, type MigrationResult, NetworkError, type OneTimeProductDiff, type OutputMode, PERMISSION_PROPAGATION_WARNING, PLACEHOLDER_TEXT, PLAY_STORE_LIMIT, PROVIDER_WHITELIST, type ParsedCommit, type ParsedManifest, type ParsedMonth, type PlayStoreFormat, type PlayStoreRenderOptions, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type Provider, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, RENDERERS, REVIEW_SORT_PRESETS, type RawCommit, type ReadinessBreakdownEntry, type ReadinessGrade, type ReadinessScore, type ReadinessSignal, type ReadinessSignalStatus, type RefundReviewPreference, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type Renderer, type ReportObject, type ReportsAuth, type ResolveAiConfigOptions, type ResolveLocalesOptions, type ReviewAnalysis, type ReviewExportOptions, type ReviewOrderRefundOptions, type ReviewOrderRefundResult, type ReviewSortPreset, type ReviewsFilterOptions, type RevocationRefundType, type RtdnStatus, SECTION_ORDER, SENSITIVE_ARG_KEYS, SENSITIVE_KEYS, SEVERITY_ORDER, type ScaffoldOptions, type ScaffoldResult, type SigningConsistencyResult, type SigningKeyComparison, type Spinner, type StatsReportDownload, type StatusDiff, type StatusRelease, type StatusReviews, type StatusVitalMetric, type SubscriptionAnalytics, type SubscriptionDiff, type SyncResult, type ThresholdResult, type TrainConfig, type TrainState, type TranslateBundleOptions, type TranslatedBundle, type TranslationFailure, type TranslationPath, type TranslationResult, type Translator, type TranslatorConfig, type UploadResult, VALID_WATCH_METRICS, type ValidateCheck, type ValidateOptions, type ValidateResult, type ValidatedPluginApproval, type VersionVitalsComparison, type VersionVitalsRow, type VitalsOverview, type VitalsQueryOptions, type VitalsTrendComparison, type WatchAction, type WatchCallbacks, type WatchConfig, type WatchEvent, type WatchMetric, type WatchOptions, type WatchRollout, type WatchSummary, type WatchVitalReading, type WatchVitalsOptions, type WebhookPayload, type ZipCsvEntry, abortTrain, acknowledgeProductPurchase, activateBasePlan, activateOffer, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, annotateListResult, applyReleaseNotes, assignRelease, batchGetOrders, batchSyncInAppProducts, buildChecklist, buildLocaleBundle, bundleToReleaseNotes, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkSigningConsistency, checkThreshold, classifyError, clearAuditLog, commitWithRescue, compareBundles, compareFingerprints, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAchievementConfig, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createLeaderboardConfig, createOffer, createOneTimeOffer, createOneTimeProduct, createRecoveryAction, createSpinner, createSubscription, createTrack, createTranslator, deactivateBasePlan, deactivateOffer, decodeCsvText, decodeNotification, decodeStatsCsv, defaultGitRunner, deferSubscriptionPurchase, deferSubscriptionV2, deleteAchievementConfig, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteLeaderboardConfig, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffAchievementConfig, diffLeaderboardConfig, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPluginEntries, discoverPlugins, dispatchWebhook, downloadFinancialReport, downloadGeneratedApk, downloadReportObject, downloadStatsReport, exportImages, exportReviews, extractCsvEntriesFromZip, fetchAggregateCost, fetchChangelog, fetchReleaseNotes, findBundle, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatMoney, formatNotification, formatOutput, formatPathLabel, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateChangelog, generateMigrationPlan, generateNotesFromGit, getAchievementConfig, getAllScannerNames, getApiSigningFingerprint, getAppInfo, getAppStatus, getCountryAvailability, getDeviceTier, getExternalTransaction, getInAppProduct, getKeystoreFingerprint, getLeaderboardConfig, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getQuotaUsage, getReleasesStatus, getReview, getRtdnStatus, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsErrorCount, getVitalsLmk, getVitalsMemory, getVitalsOverview, getVitalsRendering, getVitalsStartup, gradeFromPercent, gunzipIfNeeded, handleBreach, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isGzip, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, isVersionedNotesDir, isZip, lintListing, lintListings, lintLocalListings, listAchievementConfigs, listAchievements, listAuditEvents, listBundles, listDeviceTiers, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboardConfigs, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listRecoveryActions, listReportObjects, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, monthToken, moreResultsFooter, normalizeFingerprint, parseAppfile, parseCommit, parseFastfile, parseGrantArg, parseKeytoolOutput, parseMonth, parseRemoteUrl, pauseTrain, promoteRelease, publish, publishEnterpriseApp, pullAchievementConfigs, pullLeaderboardConfigs, pullListings, pushAchievementConfigs, pushLeaderboardConfigs, pushListings, readListingsFromDir, readReleaseNotesForVersion, readReleaseNotesFromDir, readinessBadgeColor, readinessBadgeMarkdown, readinessBadgeUrl, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, relativeTime, removeTesters, removeUser, renderChecklistMarkdown, renderJson, renderMarkdown, renderPlayStore, renderPlayStoreJson, renderPlayStoreMd, renderPlayStorePrompt, renderPrompt, replyToReview, reportAccessDeniedError, reportBucketNotFoundError, reportObjectNotFoundError, reportPrefix, resolveAiConfig, resolveLocales, resolveReportsBucket, retryOnUploadNotComplete, reviewOrderRefund, revokeSubscriptionPurchase, runPreflight, runWatch, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, scoreReadiness, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, setAchievementIcon, setLeaderboardIcon, sha256File, sortResults, sortReviews, startTrain, statusHasBreach, syncImages, syncInAppProducts, topFiles, trackBreachState, translateBundle, updateAchievementConfig, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateLeaderboardConfig, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateAndCommit, validateBundleForApply, validateImage, validateLanguageCode, validatePackageName, validatePluginForApproval, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, waitForBundle, waitForBundleProcessing, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
package/dist/index.js CHANGED
@@ -1324,7 +1324,7 @@ async function listImages(client, packageName, language, imageType) {
1324
1324
  throw error;
1325
1325
  }
1326
1326
  }
1327
- async function uploadImage(client, packageName, language, imageType, filePath, commitOptions) {
1327
+ async function uploadImage(client, packageName, language, imageType, filePath, commitOptions, uploadOptions) {
1328
1328
  validateLanguage(language);
1329
1329
  const imageCheck = await validateImage(filePath, imageType);
1330
1330
  if (!imageCheck.valid) {
@@ -1340,7 +1340,14 @@ async function uploadImage(client, packageName, language, imageType, filePath, c
1340
1340
  }
1341
1341
  const edit = await client.edits.insert(packageName);
1342
1342
  try {
1343
- const image = await client.images.upload(packageName, edit.id, language, imageType, filePath);
1343
+ const image = await client.images.upload(
1344
+ packageName,
1345
+ edit.id,
1346
+ language,
1347
+ imageType,
1348
+ filePath,
1349
+ uploadOptions
1350
+ );
1344
1351
  await validateAndCommit(client, packageName, edit.id, commitOptions);
1345
1352
  return image;
1346
1353
  } catch (error) {
@@ -3401,6 +3408,7 @@ async function runWatchLoop(opts) {
3401
3408
  const status = await opts.fetch();
3402
3409
  await opts.save(status);
3403
3410
  console.log(opts.render(status));
3411
+ await opts.onStatus?.(status);
3404
3412
  } catch (err) {
3405
3413
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
3406
3414
  }
@@ -3978,6 +3986,87 @@ async function deferSubscriptionV2(client, packageName, token, deferDuration, et
3978
3986
  deferralContext: { etag: resolvedEtag, deferDuration }
3979
3987
  });
3980
3988
  }
3989
+ var REFUND_PREFERENCES = {
3990
+ approve: "APPROVE",
3991
+ decline: "DECLINE",
3992
+ neutral: "NEUTRAL"
3993
+ };
3994
+ var MAX_CONSUMPTION_USAGE_EVENTS = 1e3;
3995
+ var MAX_CONSUMPTION_MILLIUNITS = 1e5;
3996
+ var RFC3339 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
3997
+ function invalidReviewRefund(message, suggestion) {
3998
+ return new GpcError(message, "ORDER_REVIEW_REFUND_INVALID", 2, suggestion);
3999
+ }
4000
+ async function reviewOrderRefund(client, packageName, orderId, options) {
4001
+ validatePackageName(packageName);
4002
+ if (!orderId.trim()) {
4003
+ throw invalidReviewRefund(
4004
+ "orderId is required",
4005
+ "Pass the order ID from the pending refund review notification"
4006
+ );
4007
+ }
4008
+ const pendingRefundToken = options.pendingRefundToken.trim();
4009
+ if (!pendingRefundToken) {
4010
+ throw invalidReviewRefund(
4011
+ "pendingRefundToken is required and cannot be empty",
4012
+ "Pass --pending-refund-token with the token from the PendingRefundReviewNotification"
4013
+ );
4014
+ }
4015
+ const refundPreference = REFUND_PREFERENCES[options.preference];
4016
+ if (!refundPreference) {
4017
+ throw invalidReviewRefund(
4018
+ `Invalid refund preference "${options.preference}"`,
4019
+ "Use --preference with one of: approve, decline, neutral"
4020
+ );
4021
+ }
4022
+ const milliunits = options.consumptionPercentageMilliunits;
4023
+ if (milliunits !== void 0) {
4024
+ if (!Number.isInteger(milliunits) || milliunits < 0 || milliunits > MAX_CONSUMPTION_MILLIUNITS) {
4025
+ throw invalidReviewRefund(
4026
+ `consumptionPercentageMilliunits must be an integer between 0 and ${MAX_CONSUMPTION_MILLIUNITS}, got ${milliunits}`,
4027
+ "Pass --consumption-percent with a value between 0 and 100"
4028
+ );
4029
+ }
4030
+ }
4031
+ const events = options.consumptionUsageEvents ?? [];
4032
+ if (events.length > MAX_CONSUMPTION_USAGE_EVENTS) {
4033
+ throw invalidReviewRefund(
4034
+ `consumptionUsageEvents has ${events.length} entries; Google Play rejects lists over ${MAX_CONSUMPTION_USAGE_EVENTS}`,
4035
+ `Trim the usage-events file to at most ${MAX_CONSUMPTION_USAGE_EVENTS} events`
4036
+ );
4037
+ }
4038
+ events.forEach((event, index) => {
4039
+ const time = event.consumptionTime;
4040
+ if (time !== void 0 && (!RFC3339.test(time) || Number.isNaN(Date.parse(time)))) {
4041
+ throw invalidReviewRefund(
4042
+ `consumptionUsageEvents[${index}].consumptionTime "${time}" is not an RFC 3339 timestamp`,
4043
+ "Use a timestamp like 2026-08-30T10:15:00Z"
4044
+ );
4045
+ }
4046
+ if (event.location && !event.location.regionCode?.trim()) {
4047
+ throw invalidReviewRefund(
4048
+ `consumptionUsageEvents[${index}].location.regionCode is required when a location is supplied`,
4049
+ 'Set a CLDR region code such as "US" on each location'
4050
+ );
4051
+ }
4052
+ });
4053
+ await client.orders.reviewRefund(packageName, orderId, {
4054
+ pendingRefundToken,
4055
+ refundPreference,
4056
+ sampleContentProvided: options.sampleContentProvided,
4057
+ ...milliunits !== void 0 ? { consumptionPercentageMilliunits: milliunits } : {},
4058
+ ...events.length > 0 ? { consumptionUsageEvents: events } : {}
4059
+ });
4060
+ return {
4061
+ packageName,
4062
+ orderId,
4063
+ refundPreference,
4064
+ sampleContentProvided: options.sampleContentProvided,
4065
+ ...milliunits !== void 0 ? { consumptionPercentageMilliunits: milliunits } : {},
4066
+ consumptionUsageEventCount: events.length,
4067
+ submitted: true
4068
+ };
4069
+ }
3981
4070
 
3982
4071
  // src/commands/pricing.ts
3983
4072
  async function convertRegionPrices(client, packageName, currencyCode, amount) {
@@ -4795,7 +4884,7 @@ async function getDeviceTier(client, packageName, configId) {
4795
4884
  }
4796
4885
  return client.deviceTiers.get(packageName, configId);
4797
4886
  }
4798
- async function createDeviceTier(client, packageName, config) {
4887
+ async function createDeviceTier(client, packageName, config, options) {
4799
4888
  if (!packageName) {
4800
4889
  throw new GpcError(
4801
4890
  "Package name is required",
@@ -4812,7 +4901,7 @@ async function createDeviceTier(client, packageName, config) {
4812
4901
  "Provide a valid config with deviceGroups."
4813
4902
  );
4814
4903
  }
4815
- return client.deviceTiers.create(packageName, config);
4904
+ return client.deviceTiers.create(packageName, config, options);
4816
4905
  }
4817
4906
 
4818
4907
  // src/commands/one-time-products.ts
@@ -4856,7 +4945,14 @@ var OTP_ID_FIELDS = /* @__PURE__ */ new Set(["productId", "packageName"]);
4856
4945
  function deriveOtpUpdateMask(data) {
4857
4946
  return Object.keys(data).filter((k) => !OTP_ID_FIELDS.has(k)).join(",");
4858
4947
  }
4859
- var OTP_OFFER_ID_FIELDS = /* @__PURE__ */ new Set(["packageName", "productId", "purchaseOptionId", "offerId"]);
4948
+ var OTP_OFFER_ID_FIELDS = /* @__PURE__ */ new Set([
4949
+ "packageName",
4950
+ "productId",
4951
+ "purchaseOptionId",
4952
+ "offerId",
4953
+ "state",
4954
+ "regionsVersion"
4955
+ ]);
4860
4956
  function deriveOtpOfferUpdateMask(data) {
4861
4957
  return Object.keys(data).filter((k) => !OTP_OFFER_ID_FIELDS.has(k)).join(",");
4862
4958
  }
@@ -4929,7 +5025,7 @@ async function createOneTimeOffer(client, packageName, productId, data, purchase
4929
5025
  }
4930
5026
  async function updateOneTimeOffer(client, packageName, productId, offerId, data, updateMask, purchaseOptionId = "-", regionsVersion) {
4931
5027
  try {
4932
- const mask = updateMask || deriveOtpOfferUpdateMask(data);
5028
+ const mask = updateMask || deriveOtpOfferUpdateMask(data) || void 0;
4933
5029
  return await client.oneTimeProducts.updateOffer(
4934
5030
  packageName,
4935
5031
  productId,
@@ -6253,6 +6349,9 @@ var manifestScanner = {
6253
6349
  requires: ["manifest"],
6254
6350
  async scan(ctx) {
6255
6351
  const manifest = ctx.manifest;
6352
+ if (!manifest) {
6353
+ throw new Error('The "manifest" scanner requires manifest in the preflight context');
6354
+ }
6256
6355
  const findings = [];
6257
6356
  const minTargetSdk = ctx.config.targetSdkMinimum;
6258
6357
  if (manifest.targetSdk < minTargetSdk) {
@@ -6568,6 +6667,9 @@ var permissionsScanner = {
6568
6667
  requires: ["manifest"],
6569
6668
  async scan(ctx) {
6570
6669
  const manifest = ctx.manifest;
6670
+ if (!manifest) {
6671
+ throw new Error('The "permissions" scanner requires manifest in the preflight context');
6672
+ }
6571
6673
  const findings = [];
6572
6674
  const allowed = new Set(ctx.config.allowedPermissions);
6573
6675
  for (const perm of manifest.permissions) {
@@ -6711,13 +6813,16 @@ var nativeLibsScanner = {
6711
6813
  requires: ["zipEntries"],
6712
6814
  async scan(ctx) {
6713
6815
  const entries = ctx.zipEntries;
6816
+ if (!entries) {
6817
+ throw new Error('The "native-libs" scanner requires zipEntries in the preflight context');
6818
+ }
6714
6819
  const findings = [];
6715
6820
  const abisFound = /* @__PURE__ */ new Set();
6716
6821
  let totalNativeSize = 0;
6717
6822
  for (const entry of entries) {
6718
- const match = LIB_PATH_RE.exec(entry.path);
6719
- if (match) {
6720
- abisFound.add(match[1]);
6823
+ const abi = LIB_PATH_RE.exec(entry.path)?.[1];
6824
+ if (abi) {
6825
+ abisFound.add(abi);
6721
6826
  totalNativeSize += entry.uncompressedSize;
6722
6827
  }
6723
6828
  }
@@ -6830,6 +6935,9 @@ var metadataScanner = {
6830
6935
  requires: ["metadataDir"],
6831
6936
  async scan(ctx) {
6832
6937
  const dir = ctx.metadataDir;
6938
+ if (!dir) {
6939
+ throw new Error('The "metadata" scanner requires metadataDir in the preflight context');
6940
+ }
6833
6941
  const findings = [];
6834
6942
  let entries;
6835
6943
  try {
@@ -6935,7 +7043,7 @@ var metadataScanner = {
6935
7043
  });
6936
7044
  }
6937
7045
  }
6938
- const defaultLang = locales.includes("en-US") ? "en-US" : locales[0];
7046
+ const defaultLang = locales.includes("en-US") ? "en-US" : locales[0] ?? "en-US";
6939
7047
  const privacyPath = join10(dir, defaultLang, "privacy_policy_url.txt");
6940
7048
  try {
6941
7049
  const url = await readFile13(privacyPath, "utf-8");
@@ -7078,6 +7186,7 @@ var secretsScanner = {
7078
7186
  requires: ["sourceDir"],
7079
7187
  async scan(ctx) {
7080
7188
  const dir = ctx.sourceDir;
7189
+ if (!dir) throw new Error('The "secrets" scanner requires sourceDir in the preflight context');
7081
7190
  const findings = [];
7082
7191
  const files = await collectSourceFiles(dir, SCAN_EXTENSIONS);
7083
7192
  for (const filePath of files) {
@@ -7090,6 +7199,7 @@ var secretsScanner = {
7090
7199
  const lines = content.split("\n");
7091
7200
  for (let i = 0; i < lines.length; i++) {
7092
7201
  const line = lines[i];
7202
+ if (line === void 0) continue;
7093
7203
  for (const pattern of SECRET_PATTERNS) {
7094
7204
  if (pattern.pattern.test(line)) {
7095
7205
  const relativePath = filePath.startsWith(dir) ? filePath.slice(dir.length + 1) : filePath;
@@ -7166,6 +7276,7 @@ var billingScanner = {
7166
7276
  requires: ["sourceDir"],
7167
7277
  async scan(ctx) {
7168
7278
  const dir = ctx.sourceDir;
7279
+ if (!dir) throw new Error('The "billing" scanner requires sourceDir in the preflight context');
7169
7280
  const findings = [];
7170
7281
  const detectedSdks = /* @__PURE__ */ new Set();
7171
7282
  const files = await collectSourceFiles(dir, SCAN_EXTENSIONS2);
@@ -7232,6 +7343,7 @@ var privacyScanner = {
7232
7343
  requires: ["sourceDir"],
7233
7344
  async scan(ctx) {
7234
7345
  const dir = ctx.sourceDir;
7346
+ if (!dir) throw new Error('The "privacy" scanner requires sourceDir in the preflight context');
7235
7347
  const findings = [];
7236
7348
  const detectedSdks = /* @__PURE__ */ new Set();
7237
7349
  const files = await collectSourceFiles(dir, SCAN_EXTENSIONS3);
@@ -7313,6 +7425,9 @@ var policyScanner = {
7313
7425
  requires: ["manifest"],
7314
7426
  async scan(ctx) {
7315
7427
  const manifest = ctx.manifest;
7428
+ if (!manifest) {
7429
+ throw new Error('The "policy" scanner requires manifest in the preflight context');
7430
+ }
7316
7431
  const findings = [];
7317
7432
  const perms = new Set(manifest.permissions);
7318
7433
  if (manifest.targetSdk >= 28) {
@@ -7422,6 +7537,9 @@ var sizeScanner = {
7422
7537
  requires: ["zipEntries"],
7423
7538
  async scan(ctx) {
7424
7539
  const entries = ctx.zipEntries;
7540
+ if (!entries) {
7541
+ throw new Error('The "size" scanner requires zipEntries in the preflight context');
7542
+ }
7425
7543
  const findings = [];
7426
7544
  const maxMb = ctx.config.maxDownloadSizeMb;
7427
7545
  const totalCompressed = entries.reduce((sum, e) => sum + e.compressedSize, 0);
@@ -7570,12 +7688,12 @@ async function runPreflight(options) {
7570
7688
  }
7571
7689
  const settled = await Promise.allSettled(applicableScanners.map((scanner) => scanner.scan(ctx)));
7572
7690
  let findings = [...earlyFindings];
7573
- for (let i = 0; i < settled.length; i++) {
7691
+ for (const [i, scanner] of applicableScanners.entries()) {
7574
7692
  const result = settled[i];
7693
+ if (!result) continue;
7575
7694
  if (result.status === "fulfilled") {
7576
7695
  findings.push(...result.value);
7577
7696
  } else {
7578
- const scanner = applicableScanners[i];
7579
7697
  findings.push({
7580
7698
  scanner: scanner.name,
7581
7699
  ruleId: "scanner-error",
@@ -8166,7 +8284,7 @@ async function fetchChangelog(options) {
8166
8284
  return releases.filter((r) => r.tag_name.startsWith("v")).map((r) => ({
8167
8285
  version: r.tag_name,
8168
8286
  title: r.name || r.tag_name,
8169
- date: r.published_at ? r.published_at.split("T")[0] : "unknown",
8287
+ date: r.published_at ? r.published_at.split("T")[0] ?? "unknown" : "unknown",
8170
8288
  body: r.body || "No release notes.",
8171
8289
  url: r.html_url
8172
8290
  }));
@@ -8456,12 +8574,14 @@ var TYPE_PRIORITY = {
8456
8574
  function clusterCommits(commits) {
8457
8575
  const n = commits.length;
8458
8576
  const parent = Array.from({ length: n }, (_, i) => i);
8577
+ const parentOf = (i) => parent[i] ?? i;
8459
8578
  const find = (i) => {
8460
- while (parent[i] !== i) {
8461
- parent[i] = parent[parent[i]];
8462
- i = parent[i];
8579
+ let cur = i;
8580
+ while (parentOf(cur) !== cur) {
8581
+ parent[cur] = parentOf(parentOf(cur));
8582
+ cur = parentOf(cur);
8463
8583
  }
8464
- return i;
8584
+ return cur;
8465
8585
  };
8466
8586
  const union = (i, j) => {
8467
8587
  const ri = find(i);
@@ -8470,20 +8590,32 @@ function clusterCommits(commits) {
8470
8590
  };
8471
8591
  const tokens = commits.map((c) => tokenize2(c.subject));
8472
8592
  for (let i = 0; i < n; i++) {
8593
+ const ci = commits[i];
8594
+ const ti = tokens[i];
8595
+ if (ci === void 0 || ti === void 0) continue;
8473
8596
  for (let j = i + 1; j < n; j++) {
8474
- if (shouldCluster(commits[i], commits[j], tokens[i], tokens[j])) union(i, j);
8597
+ const cj = commits[j];
8598
+ const tj = tokens[j];
8599
+ if (cj === void 0 || tj === void 0) continue;
8600
+ if (shouldCluster(ci, cj, ti, tj)) union(i, j);
8475
8601
  }
8476
8602
  }
8477
8603
  const groups = /* @__PURE__ */ new Map();
8478
8604
  for (let i = 0; i < n; i++) {
8605
+ const commit = commits[i];
8606
+ if (commit === void 0) continue;
8479
8607
  const root = find(i);
8480
- if (!groups.has(root)) groups.set(root, []);
8481
- groups.get(root).push(commits[i]);
8608
+ let group = groups.get(root);
8609
+ if (!group) {
8610
+ group = [];
8611
+ groups.set(root, group);
8612
+ }
8613
+ group.push(commit);
8482
8614
  }
8483
8615
  const clusters = [];
8484
8616
  for (const [, members] of groups) {
8485
8617
  const weight = members.reduce((s, m) => s + m.weight, 0);
8486
- const primaryType = members.map((m) => m.type).sort((a, b) => (TYPE_PRIORITY[a] ?? 99) - (TYPE_PRIORITY[b] ?? 99))[0];
8618
+ const primaryType = members.map((m) => m.type).sort((a, b) => (TYPE_PRIORITY[a] ?? 99) - (TYPE_PRIORITY[b] ?? 99))[0] ?? "other";
8487
8619
  const label = clusterLabel(members);
8488
8620
  clusters.push({
8489
8621
  id: label.toLowerCase().replace(/\s+/g, "-"),
@@ -8526,7 +8658,7 @@ function clusterLabel(members) {
8526
8658
  bestTokenCount = c;
8527
8659
  }
8528
8660
  }
8529
- return bestToken ?? members[0].subject.slice(0, 30);
8661
+ return bestToken ?? members[0]?.subject.slice(0, 30) ?? "changes";
8530
8662
  }
8531
8663
  function scoreHeadlines(clusters) {
8532
8664
  return [...clusters].sort((a, b) => {
@@ -8595,8 +8727,8 @@ async function generateChangelog(opts = {}, runner = defaultGitRunner) {
8595
8727
  const headlineCandidates = scoreHeadlines(clusters);
8596
8728
  const grouped = {};
8597
8729
  for (const c of visible) {
8598
- if (!grouped[c.type]) grouped[c.type] = [];
8599
- grouped[c.type].push(c);
8730
+ const bucket = grouped[c.type] ??= [];
8731
+ bucket.push(c);
8600
8732
  }
8601
8733
  warnings.push(...lintJargon(visible));
8602
8734
  return {
@@ -9242,6 +9374,9 @@ var OTP_NOTIFICATION_TYPES = {
9242
9374
  1: "ONE_TIME_PRODUCT_PURCHASED",
9243
9375
  2: "ONE_TIME_PRODUCT_CANCELED"
9244
9376
  };
9377
+ var REFUND_REASONS = {
9378
+ 7: "CHARGEBACK"
9379
+ };
9245
9380
  async function getRtdnStatus(client, packageName) {
9246
9381
  try {
9247
9382
  const edit = await client.edits.insert(packageName);
@@ -9308,6 +9443,20 @@ function formatNotification(notification) {
9308
9443
  purchaseToken: n.purchaseToken.slice(0, 16) + "..."
9309
9444
  };
9310
9445
  }
9446
+ if (notification.pendingRefundReviewNotification) {
9447
+ const n = notification.pendingRefundReviewNotification;
9448
+ return {
9449
+ ...base,
9450
+ type: "pending-refund-review",
9451
+ event: "PENDING_REFUND_REVIEW",
9452
+ orderId: n.orderId,
9453
+ // An absent reason is plain "UNKNOWN"; only an unrecognised value is worth echoing.
9454
+ refundReason: n.refundReason == null ? "UNKNOWN" : REFUND_REASONS[n.refundReason] ?? `UNKNOWN(${n.refundReason})`,
9455
+ // A partial notification (hand-crafted, or a Google field we have not seen
9456
+ // yet) must not crash the decoder.
9457
+ pendingRefundToken: typeof n.pendingRefundToken === "string" ? n.pendingRefundToken.slice(0, 16) + "..." : "(missing)"
9458
+ };
9459
+ }
9311
9460
  if (notification.testNotification) {
9312
9461
  return { ...base, type: "test", event: "TEST_NOTIFICATION" };
9313
9462
  }
@@ -9537,7 +9686,14 @@ async function syncImages(client, packageName, dir, options) {
9537
9686
  "Check image dimensions, file size, and format."
9538
9687
  );
9539
9688
  }
9540
- await client.images.upload(packageName, edit.id, language, imageType, filePath);
9689
+ await client.images.upload(
9690
+ packageName,
9691
+ edit.id,
9692
+ language,
9693
+ imageType,
9694
+ filePath,
9695
+ options?.aiGenerated === void 0 ? void 0 : { aiGenerated: options.aiGenerated }
9696
+ );
9541
9697
  }
9542
9698
  uploaded++;
9543
9699
  details.push({ language, imageType, file, action: "upload", reason });
@@ -9555,7 +9711,7 @@ async function syncImages(client, packageName, dir, options) {
9555
9711
  }
9556
9712
  continue;
9557
9713
  }
9558
- const localSeq = localFiles.map((file) => localHashes.get(file));
9714
+ const localSeq = localFiles.map((file) => localHashes.get(file) ?? "");
9559
9715
  const remoteSeq = remoteImages.map((img) => img.sha256.toLowerCase());
9560
9716
  const inOrder = localSeq.length === remoteSeq.length && localSeq.every((hash, i) => hash === remoteSeq[i]);
9561
9717
  if (inOrder) {
@@ -9593,7 +9749,7 @@ async function syncImages(client, packageName, dir, options) {
9593
9749
  }
9594
9750
  for (const file of localFiles) {
9595
9751
  const hash = localHashes.get(file);
9596
- if (remoteSha256Set.has(hash)) {
9752
+ if (hash !== void 0 && remoteSha256Set.has(hash)) {
9597
9753
  skipped++;
9598
9754
  details.push({ language, imageType, file, action: "skip", reason: "sha256 match" });
9599
9755
  } else {
@@ -9656,7 +9812,8 @@ async function checkSigningConsistency(accessToken, packageName, apiHost = "andr
9656
9812
  }
9657
9813
  const bundlesData = await bundlesResp.json();
9658
9814
  const bundles = (bundlesData.bundles ?? []).sort((a, b) => b.versionCode - a.versionCode);
9659
- if (bundles.length === 0) {
9815
+ const [current, previous] = bundles;
9816
+ if (!current) {
9660
9817
  throw new GpcError(
9661
9818
  "No bundles found for this app",
9662
9819
  "NO_BUNDLES",
@@ -9664,9 +9821,9 @@ async function checkSigningConsistency(accessToken, packageName, apiHost = "andr
9664
9821
  "Upload at least one AAB with 'gpc publish' before checking signing consistency"
9665
9822
  );
9666
9823
  }
9667
- const currentVc = bundles[0].versionCode;
9824
+ const currentVc = current.versionCode;
9668
9825
  const currentFp = await fetchFingerprint(baseUrl, accessToken, currentVc);
9669
- if (bundles.length === 1) {
9826
+ if (!previous) {
9670
9827
  return {
9671
9828
  currentVersionCode: currentVc,
9672
9829
  currentFingerprint: currentFp,
@@ -9674,7 +9831,7 @@ async function checkSigningConsistency(accessToken, packageName, apiHost = "andr
9674
9831
  firstRelease: true
9675
9832
  };
9676
9833
  }
9677
- const previousVc = bundles[1].versionCode;
9834
+ const previousVc = previous.versionCode;
9678
9835
  const previousFp = await fetchFingerprint(baseUrl, accessToken, previousVc);
9679
9836
  const consistent = normalizeFingerprint(currentFp) === normalizeFingerprint(previousFp);
9680
9837
  return {
@@ -9724,6 +9881,8 @@ async function fetchFingerprint(baseUrl, accessToken, versionCode) {
9724
9881
  var PLAY_CONSOLE_SETTINGS = "https://play.google.com/console/developers/settings";
9725
9882
  var VERIFICATION_PAGE = "https://developer.android.com/developer-verification";
9726
9883
  var PLAY_APP_SIGNING = "https://support.google.com/googleplay/android-developer/answer/9842756";
9884
+ var QUALITY_REQUIREMENTS_ANNOUNCEMENT = "https://android-developers.googleblog.com/2026/08/app-quality-memory-optimization-secure-onboarding.html";
9885
+ var RESTORE_CREDENTIALS_DOCS = "https://developer.android.com/identity/sign-in/restore-credentials";
9727
9886
  function buildChecklist(input) {
9728
9887
  const items = [];
9729
9888
  const answers = input.interactiveAnswers ?? {};
@@ -9770,6 +9929,24 @@ function buildChecklist(input) {
9770
9929
  answers
9771
9930
  )
9772
9931
  );
9932
+ items.push(
9933
+ resolveManualStep(
9934
+ "memory-quality-thresholds",
9935
+ "Memory quality requirements reviewed",
9936
+ "Starting February 2027, apps and games must meet Google Play's dynamic memory, bitmap memory, and DEX code optimization thresholds (at least 25% coverage using R8 or another shrinking tool); apps that miss them may see reduced visibility and publishing capabilities. Review the dynamic memory metrics, out of memory crash filters, and DEX optimization insights in Play Console under Android vitals. Source: " + QUALITY_REQUIREMENTS_ANNOUNCEMENT,
9937
+ QUALITY_REQUIREMENTS_ANNOUNCEMENT,
9938
+ answers
9939
+ )
9940
+ );
9941
+ items.push(
9942
+ resolveManualStep(
9943
+ "restore-credentials",
9944
+ "Zero-Tap Sign-In (Restore Credentials API) implemented",
9945
+ "Starting April 2027, apps that support user sign-in must restore the signed-in state when a user moves to a new Android device, using the Android Restore Credentials API, to keep full publishing capabilities and optimal Play Store visibility. Games are currently exempt. Adopt the Restore Credentials API in your sign-in flow. Source: " + QUALITY_REQUIREMENTS_ANNOUNCEMENT,
9946
+ RESTORE_CREDENTIALS_DOCS,
9947
+ answers
9948
+ )
9949
+ );
9773
9950
  if (input.appAccessible !== void 0) {
9774
9951
  items.push({
9775
9952
  id: "app-accessible",
@@ -10173,6 +10350,7 @@ export {
10173
10350
  resolveLocales,
10174
10351
  resolveReportsBucket,
10175
10352
  retryOnUploadNotComplete,
10353
+ reviewOrderRefund,
10176
10354
  revokeSubscriptionPurchase,
10177
10355
  runPreflight,
10178
10356
  runWatch,